﻿using System.ServiceProcess;
using System.Linq;
using System;
using System.Reflection;
using System.Threading;
using System.Collections.Generic;

namespace AppService {
    public partial class Service : ServiceBase {
        public Service() {
            InitializeComponent();
        }

        private Timer p_tmrLoad;
        private System.IO.FileSystemWatcher m_Watcher;
        private Worker p_Worker;
        private AppDomain p_Domain;
        private string p_WatchDir;

        private object p_ReloadLock = new object();
        private object p_UnloadLock = new object();
        private long p_LoadLock = 0;

        private static string GetAppSetting(string name, string @default) {
            string value = GetAppSetting(name);
            return string.IsNullOrEmpty(value) ? @default : value;
        }
        private static string GetAppSetting(string name) {
            return System.Configuration.ConfigurationManager.AppSettings[name];
        }

        public void ManualStart() {
            p_WatchDir = GetAppSetting("WatchFolder");
            m_Watcher = new System.IO.FileSystemWatcher(p_WatchDir);
            m_Watcher.EnableRaisingEvents = true;
            m_Watcher.Changed += new System.IO.FileSystemEventHandler(m_Watcher_Changed);
            m_Watcher.Deleted += new System.IO.FileSystemEventHandler(m_Watcher_Changed);
            m_Watcher.Created += new System.IO.FileSystemEventHandler(m_Watcher_Changed);
            m_Watcher.Renamed += new System.IO.RenamedEventHandler(m_Watcher_Renamed);

            Load();
        }

        protected override void OnStart(string[] args) {
            ManualStart();
        }

        protected override void OnStop() {
            Unload();
            if (m_Watcher != null) m_Watcher.Dispose();
        }

        public void Load() {
            //Prevent the method from being called multiple times simultaneously
            //This is different from other locks because the method shouldn't be called multiple times.  If it is, the subsequent calls should be ignored and not executed.
            if (Interlocked.Increment(ref p_LoadLock) == 1) { 
                Program.Try(() => {
                    p_Domain = AppDomain.CreateDomain("AppServiceHost", new System.Security.Policy.Evidence(AppDomain.CurrentDomain.Evidence), p_WatchDir, string.Empty, true);
                    var obj = p_Domain.CreateInstanceFrom(Assembly.GetExecutingAssembly().Location, typeof(Worker).FullName);
                    p_Worker = (Worker)obj.Unwrap();
                    p_Worker.Initialize(GetAppSetting("ServiceType", "AE.Remoting.Service"));
                }, 10, 1000);

                if (p_Worker == null) throw new Exception("Unable to load AppDomain and/or worker objects!");
            }
            Interlocked.Decrement(ref p_LoadLock);
        }

        private void m_Watcher_Renamed(object sender, System.IO.RenamedEventArgs e) {
            Reload();
        }

        private void m_Watcher_Changed(object sender, System.IO.FileSystemEventArgs e) {
            Reload();
        }

        public void Unload() {
            if (p_Domain == null) return;
            if (!Monitor.TryEnter(p_UnloadLock)) return; // This method is already being called

            Program.Try(() => {
                if (p_Worker != null) p_Worker.Dispose();
                p_Worker = null;
                AppDomain.Unload(p_Domain);
                p_Domain = null;
            }, 10, 1000);

            Monitor.Exit(p_UnloadLock);

            if (p_Domain != null) {
                throw new Exception("AppDomain couldn't be unloaded!");
            }
        }

        private void Reload() {
            if (!Monitor.TryEnter(p_ReloadLock)) return; // This method is already being called

            ManualResetEvent mre = new ManualResetEvent(false);
            while (p_LoadLock > 0) { //The Load() method is executing, so wait until it's finished
                mre.WaitOne(100);
            }

            try {
                Unload();
                ScheduleLoad();

            } catch (Exception) {
                throw;
            } finally {
                Monitor.Exit(p_ReloadLock);
            }
        } 

        private void ScheduleLoad() {
            if (p_LoadLock > 0) return; //The Load() method is currently executing

            if (p_tmrLoad != null) p_tmrLoad.Dispose(); 
            p_tmrLoad = new Timer(x => {
                try {
                    System.Diagnostics.EventLog.WriteEntry("AppService", "Restarting!", System.Diagnostics.EventLogEntryType.Information, 100);
                    Load();
                    System.Diagnostics.EventLog.WriteEntry("AppService", "Restarted!", System.Diagnostics.EventLogEntryType.Information, 101);
                } catch (Exception ex) {
                    System.Diagnostics.EventLog.WriteEntry("AppService", "Error on ScheduleLoad: " + ex.ToString(), System.Diagnostics.EventLogEntryType.Error, 103);
                }  
            }, null, 2000, Timeout.Infinite);
        }
    }
}